最近项目需要用到jni去实现一些功能,在原生层调用java层的过程中,踩了很多坑,这里做个记录,方便以后查阅,以及如果遇到一些错误,看看有没有这里的坑。
#坑1–jni方法签名分号问题
这是一个很常见的问题,第一次写jni中方法签名的时候很容易遇到这个错误,比如下面这个签名
jmethodID equals_id = env->GetMethodID(string_c, "equals", "(Ljava/lang/Object;)Z");
切记当参数或者返回值不是基本类型的时候一定要在末尾加上分号。
jmethodID iterator_id = env->GetMethodID(list_c,"iterator","()Ljava/util/Iterator;");
这类错误常见描述为方法找不到错误。如果遇到类方法找不到切记先检查方法签名是否正确。
#坑2–原生方法包含默认2个参数
这个坑当然是对jni使用不熟练造成的,未了解其特性,所以对于初学者来说容易犯。看一个原生函数
static jboolean nativeFunction(JNIEnv *env, jobject content, jobject someObj){...}
第一个保留参数为JNIEnv
虚拟机环境变量,第二个为调用该原生方法的java类对象即上下文。这里多说一句,第二个参数的上下文需根据该方法为静态方法还是实例方法,若为静态方法,则这里上下文为类引用;若为实例方法,则上下文为该类的对象引用。若把类引用当做对象引用,在查找方法id时则会出现方法找不到错误。
由于很多时候不声明这两个参数同样能正确运行(无其他参数时)导致忽略了第二个参数的存在,以为第二个上下文参数为传入的对象引用。
这类错误常见描述为当你在某处调用这个对象的方法时,会提示找不到该方法。因为这个对象根本不是你传入的对象。
#坑3—jni调用父类方法的正确姿势
这里就以一个正确的调用父类方法的流程来讲,起因是当我在查找Exception
这个类的getStackTrace
方法时提示方法找不到错误,调了会才发现这个方法是在其父类Throwable
中,所以正确的写法为:
jclass exception_c = env->FindClass("java/lang/Exception");
jclass throwable_c = env->FindClass("java/lang/Throwable");
jmethodID getStackTrace_id = env->GetMethodID(throwable_c, "getStackTrace","()[Ljava/lang/StackTraceElement;");
那么怎么调用呢?用CallNonvirtual<XXX>Method
去调用父类中的方法,一个调用例子:
jobjectArray stes = (jobjectArray)env->CallNonvirtualObjectMethod(excep, throwable_c, getStackTrace_id);
这里需要传入三个参数,第一个为子类的对象引用,第二个为父类类引用,第三个为方法id。
#坑4—其他小坑
写jni代码时最大的麻烦在于要写很多代码去定位java中的类和方法,java中一个很简单的调用,在jni层写时或许要写很大一堆,这也不奇怪,因为没有了Android虚拟机,这些本来由虚拟机做的事必须得手动去完成。在写jni层代码时,还有一些小的错误,比如jstring
与char*
的转换,LOGE()
函数中参数为char*
的字符串。当然还有一些java与C语言类型对应的问题就不多说了,自己翻下jni.h
头文件就能找到。
还有个AndroidStudio的bug在于不能正确的解析jni等头文件,虽然不影响编译,但没有了代码提示以及显示红色看着也挺难受的。网上找到一个解决办法,在对应的build.gradle
文件中在defaultConfig{}
加入下面的脚本:
sourceSets.main{
jni.srcDirs '/Users/king/Android/sdk/ndk-bundle/platforms/android-19/arch-mips/usr/include'
jniLibs.srcDir '/Users/king/Android/sdk/ndk-bundle/platforms/android-19/arch-arm/usr/lib'
}
无非是手动指定了jni相关的文件路径。
关于下面两类写法:
jclass exception_c = env->FindClass("java/lang/Exception");
jclass exception_c = (*env)->FindClass(env, "java/lang/Exception");
原因在于C++和C的两种写法,可以在文件中宏定义为C++的即可。
#完
说了这些坑,当然我都踩过,由于刚接触Android jni特性。所以踩了很多坑,尤其是Android不像iOS那样容易debug,多了一层虚拟机导致很多错误不好调式。说到这,还要吐槽下AndroidStudio的gradle这个项目管理器,各种版本或者莫名奇怪的错误,虽然Xcode同样不是很好用,但相比之下,Xcode省心多了。
继续踩坑去了…